{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Recommender Code Along\n",
    "\n",
    "The classic recommender tutorial uses the [movielens data set](https://grouplens.org/datasets/movielens/). It is similar to using the iris or MNIST data set for other algorithms. Let's do a code along to get an idea of how this all works!\n",
    "\n",
    "\n",
    "Looking for more datasets? Check out: https://gist.github.com/entaroadun/1653794"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from pyspark.sql import SparkSession"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "spark = SparkSession.builder.appName('rec').getOrCreate()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With Collaborative filtering we make predictions (filtering) about the interests of a user by collecting preferences or taste information from many users (collaborating). The underlying assumption is that if a user A has the same opinion as a user B on an issue, A is more likely to have B's opinion on a different issue x than to have the opinion on x of a user chosen randomly.\n",
    "\n",
    "The image below (from Wikipedia) shows an example of collaborative filtering. At first, people rate different items (like videos, images, games). Then, the system makes predictions about a user's rating for an item not rated yet. The new predictions are built upon the existing ratings of other users with similar ratings with the active user. In the image, the system predicts that the user will not like the video.\n",
    "\n",
    "<img src=https://upload.wikimedia.org/wikipedia/commons/5/52/Collaborative_filtering.gif />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Spark MLlib library for Machine Learning provides a Collaborative Filtering implementation by using Alternating Least Squares. The implementation in MLlib has these parameters:\n",
    "\n",
    "* numBlocks is the number of blocks used to parallelize computation (set to -1 to auto-configure).\n",
    "* rank is the number of latent factors in the model.\n",
    "* iterations is the number of iterations to run.\n",
    "* lambda specifies the regularization parameter in ALS.\n",
    "* implicitPrefs specifies whether to use the explicit feedback ALS variant or one adapted for implicit feedback data.\n",
    "* alpha is a parameter applicable to the implicit feedback variant of ALS that governs the baseline confidence in preference observations.\n",
    "\n",
    "Let's see this all in action!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from pyspark.ml.evaluation import RegressionEvaluator\n",
    "from pyspark.ml.recommendation import ALS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data = spark.read.csv('movielens_ratings.csv',inferSchema=True,header=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Row(movieId=2, rating=3.0, userId=0)"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------+------------------+------------------+------------------+\n",
      "|summary|           movieId|            rating|            userId|\n",
      "+-------+------------------+------------------+------------------+\n",
      "|  count|              1501|              1501|              1501|\n",
      "|   mean| 49.40572951365756|1.7741505662891406|14.383744170552964|\n",
      "| stddev|28.937034065088994| 1.187276166124803| 8.591040424293272|\n",
      "|    min|                 0|               1.0|                 0|\n",
      "|    max|                99|               5.0|                29|\n",
      "+-------+------------------+------------------+------------------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "data.describe().show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can do a split to evaluate how well our model performed, but keep in mind that it is very hard to know conclusively how well a recommender system is truly working for some topics. Especially if subjectivity is involved, for example not everyone that loves star wars is going to love star trek, even though a recommendation system may suggest otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Smaller dataset so we will use 0.8 / 0.2\n",
    "(training, test) = data.randomSplit([0.8, 0.2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Build the recommendation model using ALS on the training data\n",
    "als = ALS(maxIter=5, regParam=0.01, userCol=\"userId\", itemCol=\"movieId\", ratingCol=\"rating\")\n",
    "model = als.fit(training)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see hwo the model performed!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Evaluate the model by computing the RMSE on the test data\n",
    "predictions = model.transform(test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------+------+------+----------+\n",
      "|movieId|rating|userId|prediction|\n",
      "+-------+------+------+----------+\n",
      "|     31|   1.0|    27| 2.5976338|\n",
      "|     31|   1.0|    13|  2.113986|\n",
      "|     31|   1.0|     5| 1.3165921|\n",
      "|     31|   2.0|    25|0.16370271|\n",
      "|     85|   1.0|    28|-2.5285664|\n",
      "|     85|   1.0|    26|0.37620115|\n",
      "|     85|   1.0|    12| 0.8253538|\n",
      "|     85|   3.0|     1| 1.6069186|\n",
      "|     85|   1.0|    13| 2.2720711|\n",
      "|     85|   5.0|    16|0.80576146|\n",
      "|     85|   1.0|    15|0.54832166|\n",
      "|     85|   1.0|     4|  3.144216|\n",
      "|     65|   1.0|    28|  -2.03051|\n",
      "|     65|   2.0|     3|  3.801642|\n",
      "|     65|   1.0|     2| 1.7128268|\n",
      "|     53|   3.0|    13| 3.4453833|\n",
      "|     53|   1.0|     6| 1.8362958|\n",
      "|     53|   1.0|     9| 1.8954519|\n",
      "|     78|   1.0|    22| 0.5302301|\n",
      "|     78|   1.0|    13| 0.5055496|\n",
      "+-------+------+------+----------+\n",
      "only showing top 20 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "predictions.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Root-mean-square error = 1.751143638387403\n"
     ]
    }
   ],
   "source": [
    "evaluator = RegressionEvaluator(metricName=\"rmse\", labelCol=\"rating\",predictionCol=\"prediction\")\n",
    "rmse = evaluator.evaluate(predictions)\n",
    "print(\"Root-mean-square error = \" + str(rmse))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The RMSE described our error in terms of the stars rating column."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So now that we have the model, how would you actually supply a recommendation to a user?\n",
    "\n",
    "The same way we did with the test data! For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "single_user = test.filter(test['userId']==11).select(['movieId','userId'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------+------+\n",
      "|movieId|userId|\n",
      "+-------+------+\n",
      "|      0|    11|\n",
      "|     13|    11|\n",
      "|     18|    11|\n",
      "|     30|    11|\n",
      "|     66|    11|\n",
      "|     70|    11|\n",
      "|     75|    11|\n",
      "|     78|    11|\n",
      "|     79|    11|\n",
      "|     99|    11|\n",
      "+-------+------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# User had 10 ratings in the test data set \n",
    "# Realistically this should be some sort of hold out set!\n",
    "single_user.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "reccomendations = model.transform(single_user)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------+------+----------+\n",
      "|movieId|userId|prediction|\n",
      "+-------+------+----------+\n",
      "|     30|    11|  5.578189|\n",
      "|     13|    11|  3.257565|\n",
      "|     70|    11| 2.7580981|\n",
      "|     99|    11| 1.7420897|\n",
      "|     18|    11| 1.5150304|\n",
      "|     75|    11|   1.34218|\n",
      "|     79|    11| 0.9733073|\n",
      "|     66|    11| 0.5732717|\n",
      "|     78|    11| 0.4434041|\n",
      "|      0|    11|  -1.85298|\n",
      "+-------+------+----------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "reccomendations.orderBy('prediction',ascending=False).show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Great Job!"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python [conda root]",
   "language": "python",
   "name": "conda-root-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
